package net.aiion.weave.impl.cards.xml;

import net.aiion.weave.asm.Opcodes;
import net.aiion.weave.spi.bytecode.BytecodeGenerator;
import net.aiion.weave.spi.cards.CardContext;
import net.aiion.weave.spi.cards.events.CardEvent;
import net.aiion.weave.spi.xml.XmlContext;
import net.aiion.weave.spi.xml.XmlGenerator;
import net.aiion.weave.spi.xml.XmlQualifiedName;
import net.aiion.weave.spi.xml.XmlSink;
import net.aiion.weave.spi.xml.events.AttributeXmlEvent;
import net.aiion.weave.spi.xml.events.CharactersXmlEvent;
import net.aiion.weave.spi.xml.events.CommentXmlEvent;
import net.aiion.weave.spi.xml.events.DocumentEndXmlEvent;
import net.aiion.weave.spi.xml.events.DocumentStartXmlEvent;
import net.aiion.weave.spi.xml.events.DocumentTypeDefinitionXmlEvent;
import net.aiion.weave.spi.xml.events.ElementEndXmlEvent;
import net.aiion.weave.spi.xml.events.ElementStartXmlEvent;
import net.aiion.weave.spi.xml.events.NamespaceXmlEvent;
import net.aiion.weave.spi.xml.events.ProcessingInstructionXmlEvent;
import net.aiion.weave.spi.xml.events.XmlEvent;

public class XmlEventTransformation implements XmlSink
{
    private static final String PROP_INTERNAL_NAME = "internalName";

    private static final int INDEX_CARD_CONTEXT = 1;

    private static final String XML_GENERATOR_FIELD_NAME = "xmlGenerator$XmlEventSink";

    private BytecodeGenerator bytecodeGenerator;

    public XmlEventTransformation() {
    }

    @Override
    public void handleXmlEvent(final XmlContext aXmlContext) {
        bytecodeGenerator = aXmlContext.getProperty("bytecodeGenerator", BytecodeGenerator.class);
        final XmlEvent xmlEvent = aXmlContext.getXmlEvent();
        switch (xmlEvent.getType()) {
        case CardEvent.CARD_CLASS_START:
            bytecodeGenerator.startField(Opcodes.ACC_PRIVATE, XML_GENERATOR_FIELD_NAME, XmlGenerator.class);
            bytecodeGenerator.endField();
            aXmlContext.forwardXmlEvent(xmlEvent);
            break;
        case CardEvent.CARD_PROCESS_CARD_METHOD_START:
            bytecodeGenerator.loadReference(0);
            bytecodeGenerator.loadReference(INDEX_CARD_CONTEXT);
            bytecodeGenerator.invokeInterface(CardContext.class, "getXmlGenerator", XmlGenerator.class);
            bytecodeGenerator.storeField(aXmlContext.getProperty(PROP_INTERNAL_NAME, String.class),
                XML_GENERATOR_FIELD_NAME, XmlGenerator.class);
            aXmlContext.forwardXmlEvent(xmlEvent);
            break;
        case XmlEvent.START_DOCUMENT:
            handleDocumentStartXmlEvent(aXmlContext, xmlEvent.asDocumentStart());
            break;
        case XmlEvent.DOCUMENT_TYPE_DEFINITION:
            handleDocumentTypeDefinitionXmlEvent(aXmlContext, xmlEvent.asDocumentTypeDefinition());
            break;
        case XmlEvent.PROCESSING_INSTRUCTION:
            handleProcessingInstructionXmlEvent(aXmlContext, xmlEvent.asProcessingInstruction());
            break;
        case XmlEvent.START_ELEMENT:
            handleElementStartXmlEvent(aXmlContext, xmlEvent.asElementStart());
            break;
        case XmlEvent.ATTRIBUTE:
            handleAttributeXmlEvent(aXmlContext, xmlEvent.asAttribute());
            break;
        case XmlEvent.NAMESPACE:
            handleNamespaceXmlEvent(aXmlContext, xmlEvent.asNamespace());
            break;
        case XmlEvent.CHARACTERS:
            handleCharactersXmlEvent(aXmlContext, xmlEvent.asCharacters());
            break;
        case XmlEvent.COMMENT:
            handleCommentXmlEvent(aXmlContext, xmlEvent.asComment());
            break;
        case XmlEvent.END_ELEMENT:
            handleElementEndXmlEvent(aXmlContext, xmlEvent.asElementEnd());
            break;
        case XmlEvent.END_DOCUMENT:
            handleDocumentEndXmlEvent(aXmlContext, xmlEvent.asDocumentEnd());
            break;
        default:
            aXmlContext.forwardXmlEvent(xmlEvent);
            break;
        }
    }

    private void handleDocumentStartXmlEvent(final XmlContext aXmlContext,
        final DocumentStartXmlEvent aDocumentStartXmlEvent)
    {
        bytecodeGenerator.loadReference(0);
        bytecodeGenerator.loadField(aXmlContext.getProperty(PROP_INTERNAL_NAME, String.class),
            XML_GENERATOR_FIELD_NAME, XmlGenerator.class);
        bytecodeGenerator.pushConstant(aDocumentStartXmlEvent.getVersion());
        bytecodeGenerator.pushConstant(aDocumentStartXmlEvent.getEncoding());
        bytecodeGenerator.pushBoolean(aDocumentStartXmlEvent.isStandalone());
        bytecodeGenerator.invokeInterface(XmlGenerator.class, "startDocument", void.class, String.class, String.class,
            boolean.class);
    }

    private void handleDocumentTypeDefinitionXmlEvent(final XmlContext aXmlContext,
        final DocumentTypeDefinitionXmlEvent aDocumentTypeDefinitionXmlEvent)
    {
        bytecodeGenerator.loadReference(0);
        bytecodeGenerator.loadField(aXmlContext.getProperty(PROP_INTERNAL_NAME, String.class),
            XML_GENERATOR_FIELD_NAME, XmlGenerator.class);
        bytecodeGenerator.pushConstant(aDocumentTypeDefinitionXmlEvent.getDocumentTypeDefinition());
        bytecodeGenerator.invokeInterface(XmlGenerator.class, "declareDocumentType", void.class, String.class);
    }

    private void handleProcessingInstructionXmlEvent(final XmlContext aXmlContext,
        final ProcessingInstructionXmlEvent aProcessingInstructionXmlEvent)
    {
        bytecodeGenerator.loadReference(0);
        bytecodeGenerator.loadField(aXmlContext.getProperty(PROP_INTERNAL_NAME, String.class),
            XML_GENERATOR_FIELD_NAME, XmlGenerator.class);
        bytecodeGenerator.pushConstant(aProcessingInstructionXmlEvent.getTarget());
        bytecodeGenerator.pushConstant(aProcessingInstructionXmlEvent.getData());
        bytecodeGenerator.invokeInterface(XmlGenerator.class, "writeProcessingInstruction", void.class, String.class,
            String.class);
    }

    private void handleElementStartXmlEvent(final XmlContext aXmlContext,
        final ElementStartXmlEvent aElementStartXmlEvent)
    {
        final XmlQualifiedName qualifiedName = aElementStartXmlEvent.getName();

        bytecodeGenerator.loadReference(0);
        bytecodeGenerator.loadField(aXmlContext.getProperty(PROP_INTERNAL_NAME, String.class),
            XML_GENERATOR_FIELD_NAME, XmlGenerator.class);
        bytecodeGenerator.pushConstant(qualifiedName.getNamespaceURI());
        bytecodeGenerator.pushConstant(qualifiedName.getLocalName());
        bytecodeGenerator.pushConstant(qualifiedName.getPrefix());
        bytecodeGenerator.invokeInterface(XmlGenerator.class, "startElement", void.class, String.class, String.class,
            String.class);

        for (AttributeXmlEvent attributeXmlEvent : aElementStartXmlEvent.getAttributes()) {
            handleAttributeXmlEvent(aXmlContext, attributeXmlEvent);
        }

        for (NamespaceXmlEvent namespaceXmlEvent : aElementStartXmlEvent.getNamespaces()) {
            handleNamespaceXmlEvent(aXmlContext, namespaceXmlEvent);
        }
    }

    private void handleAttributeXmlEvent(final XmlContext aXmlContext, final AttributeXmlEvent aAttributeXmlEvent) {
        final XmlQualifiedName attributeQualifiedName = aAttributeXmlEvent.getName();

        bytecodeGenerator.loadReference(0);
        bytecodeGenerator.loadField(aXmlContext.getProperty(PROP_INTERNAL_NAME, String.class),
            XML_GENERATOR_FIELD_NAME, XmlGenerator.class);
        bytecodeGenerator.pushConstant(attributeQualifiedName.getNamespaceURI());
        bytecodeGenerator.pushConstant(attributeQualifiedName.getLocalName());
        bytecodeGenerator.pushConstant(attributeQualifiedName.getPrefix());
        bytecodeGenerator.pushConstant(aAttributeXmlEvent.getValue());
        bytecodeGenerator.invokeInterface(XmlGenerator.class, "addAttribute", void.class, String.class, String.class,
            String.class, String.class);
    }

    private void handleNamespaceXmlEvent(final XmlContext aXmlContext, final NamespaceXmlEvent aNamespaceXmlEvent) {
        bytecodeGenerator.loadReference(0);
        bytecodeGenerator.loadField(aXmlContext.getProperty(PROP_INTERNAL_NAME, String.class),
            XML_GENERATOR_FIELD_NAME, XmlGenerator.class);
        bytecodeGenerator.pushConstant(aNamespaceXmlEvent.getPrefix());
        bytecodeGenerator.pushConstant(aNamespaceXmlEvent.getNamespaceURI());
        bytecodeGenerator.invokeInterface(XmlGenerator.class, "addNamespace", void.class, String.class, String.class);
    }

    private void handleCharactersXmlEvent(final XmlContext aXmlContext, final CharactersXmlEvent aCharactersXmlEvent) {
        bytecodeGenerator.loadReference(0);
        bytecodeGenerator.loadField(aXmlContext.getProperty(PROP_INTERNAL_NAME, String.class),
            XML_GENERATOR_FIELD_NAME, XmlGenerator.class);
        bytecodeGenerator.pushConstant(aCharactersXmlEvent.getData());
        bytecodeGenerator.pushInteger(aCharactersXmlEvent.getCharacterDataType());
        bytecodeGenerator.invokeInterface(XmlGenerator.class, "writeCharacters", void.class, String.class, int.class);
    }

    private void handleCommentXmlEvent(final XmlContext aXmlContext, final CommentXmlEvent aCommentXmlEvent) {
        bytecodeGenerator.loadReference(0);
        bytecodeGenerator.loadField(aXmlContext.getProperty(PROP_INTERNAL_NAME, String.class),
            XML_GENERATOR_FIELD_NAME, XmlGenerator.class);
        bytecodeGenerator.pushConstant(aCommentXmlEvent.getComment());
        bytecodeGenerator.invokeInterface(XmlGenerator.class, "writeComment", void.class, String.class);
    }

    private void handleElementEndXmlEvent(final XmlContext aXmlContext, final ElementEndXmlEvent aElementEndXmlEvent) {
        bytecodeGenerator.loadReference(0);
        bytecodeGenerator.loadField(aXmlContext.getProperty(PROP_INTERNAL_NAME, String.class),
            XML_GENERATOR_FIELD_NAME, XmlGenerator.class);
        bytecodeGenerator.invokeInterface(XmlGenerator.class, "endElement", void.class);
    }

    private void
        handleDocumentEndXmlEvent(final XmlContext aXmlContext, final DocumentEndXmlEvent aDocumentEndXmlEvent)
    {
        bytecodeGenerator.loadReference(0);
        bytecodeGenerator.loadField(aXmlContext.getProperty(PROP_INTERNAL_NAME, String.class),
            XML_GENERATOR_FIELD_NAME, XmlGenerator.class);
        bytecodeGenerator.invokeInterface(XmlGenerator.class, "endDocument", void.class);
    }
}
